package source.model;

import java.util.*;
import source.model.type.GameObjectType;
import source.model.utility.EatAtJoesIterator;
import source.events.EventHandler;
import source.events.ViewEvent;
import source.events.ViewObjectEvent;
import source.events.ViewEventType;
import source.view.MainScreen;

public class DataHandler {

	private Vector<GameData> dataList;
	private PositionMap pMap;
	private OwnershipMap oMap;
	private Vector<Player> players;
	private ObjectID currentSelection;
	private Position currentPosition;
	private EventHandler<ViewEvent> viewHandler;
	
	
	private EatAtJoesIterator playerUnits, playerBases;
	
	// Here's a clever idea, have the DataHandler object map object's to their
	// respective owner. We could make another class similiar to the PositionMap
	// class that we could query for all ObjectID's owned by a player or request
	// the pid for a particular GameObject... interesting...
	
	public DataHandler(EventHandler<ViewEvent> veh) {
		dataList = new Vector<GameData>();
		players = new Vector<Player>();
		viewHandler = veh;
		
		// cycles through the enumeration list and creates a new GameData object
		// for each needed data type
		for ( GameObjectType got : GameObjectType.values() ) {
			dataList.add(GameDataFactory.createGameData(got));
		}
		
		pMap = new PositionMap();
		oMap = new OwnershipMap();
	}
	
	public boolean addGameObject( GameObject go, Position p ) {
		if ( go.isPlayerOwned() ) {
			System.out.println("Game object is a player owned object.");
			return false;
		}
		this.pMap.addGameObject(go, p);
		for ( GameData gd : dataList ) {
			if ( gd.addObject( go ) )
			{
				ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.ADD_VIEW_OBJECT, go, p);
				viewHandler.fireEventNow(voe);
				
				return true;
			}
		}
		return false;
	}
	
	public boolean addGameObject( GameObject go, Position p, int playerId ) {
		if ( !go.isPlayerOwned() ) {
			System.out.println("Game object is not a player owned object.");
			return false;
		}

		if(!validatePlayerID(playerId))
		{
			System.out.println(playerId + " is not a valid Player ID");
			return false;
		}
		this.pMap.addGameObject(go, p);
		this.oMap.addGameObject(go, playerId);
		
		for ( GameData gd : dataList ) {
			if ( gd.addObject( go ) )
			{
				ViewObjectEvent voe = new ViewObjectEvent(playerId, ViewEventType.ADD_VIEW_OBJECT, go, p);
				viewHandler.fireEventNow(voe);
				
				return true;
			}
		}
		return false;
	}
	
	public Position positionQuery( ObjectID id ) {
		return pMap.getPosition( id );
	}
	
	public Tile positionQuery( Position p ) {
		ObjectID[] oids = pMap.getAt( p );
		Tile ret = new Tile();
		for ( ObjectID oid : oids ) {
			ret.add( objectQuery( oid ) );
			if (objectQuery(oid).isPlayerOwned()) 
				ret.setPID( oMap.getPlayerID(oid) );
		}
		return ret;
	}
	
	public GameObject[] positionQuery( Position p, GameObjectType got ) {
		ObjectID[] oids = getIDsAt( p );
		
		ArrayList<GameObject> gos = new ArrayList<GameObject>();
		for ( ObjectID oid : oids ) {
			GameObject g = objectQuery( oid );
			if ( checkObject(g) && g.objectType() == got ) {
				gos.add( g );
			}
		}
		GameObject[] ret = new GameObject[gos.size()];
		int i = 0;
		for ( GameObject g : gos ) {
			ret[i++] = g;
		}
		
		return ret;
	}
	
	private ObjectID[] getIDsAt( Position p ) {
		return pMap.getAt( p );
	}
	
	public int playerIDQuery(ObjectID oid)
	{
		return oMap.getPlayerID(oid);
	}
	
	public int playerIDQuery(GameObject go)
	{
		return oMap.getPlayerID(go);
	}
	
	public Player playerQuery(GameObject go)
	{
		int playerId = oMap.getPlayerID(go);
		return getPlayer(playerId);
	}
	
	public GameObject objectQuery( ObjectID id ) {
		GameObject ret = null;
		for ( GameData gd : this.dataList ) {
			ret = gd.getObject( id );
			if ( ret != null ) {
				if (!ret.isValid() || ret.changePositionFlag()) {
					return null;
				}
				break;
			}
		}		
		return ret;
	}
	
	public Vector<GameObject> objectQuery(GameObjectType got)
	{
		Vector<GameObject> ret = null;
		for (GameData gd : this.dataList ) {
			ret = gd.getAllObjects(got);
			if ( ret != null) break;
		}
		//TODO:  make a checkObject for a Vector<GameObject>
		return ret;
	}
	public boolean positionCommand( ObjectID id, Position newPos ) {
		if ( pMap.changePosition(id, newPos) )
		{
			GameObject g = objectQuery(id);
			this.setCurrentPosition(newPos);
			ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.MOVE_VIEW_OBJECT, g, newPos);
			viewHandler.fireEventNow(voe);
			
			return true;
		}
		return false;
	}
	
	public void returnObject( GameObject g ) {
		checkObject( g );
		ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.MODIFY_VIEW_OBJECT, g, null);
		viewHandler.fireEventNow(voe);		
	}
	
	public void returnObjects( GameObject[] gs ) {
		for ( GameObject go : gs ) {
			returnObject(go);
		}
	}

	public boolean unassign( GameObject go ) {
		
		ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.REMOVE_VIEW_OBJECT, go, null);
		viewHandler.fireEventNow(voe);
		oMap.removeObject( go );
		pMap.removeObject( go );
		return true;
	}

	public boolean reassign( GameObject go, Position newPos) {
		ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.ADD_VIEW_OBJECT, go, newPos);
		viewHandler.fireEventNow(voe);
		return addGameObject(go, newPos);
	}

	public boolean unassign(GameObject go, GameObject[] gos, Position newPos, int pid){
		if(!addGameObject(go, newPos, pid))
			return false;

		for(GameObject g : gos){
			removeObject(g);
		}

		return true;
	}
				
	public boolean reassign(GameObject go, GameObject[] gos, Position newPos, int pid){
		removeObject(go);

		boolean failed = false;
		for(GameObject g : gos){
			if(!addGameObject(g, newPos, pid))
				failed = true;
		}

		return failed;

	}

	private boolean checkObject( GameObject g ) {
		boolean ret = true;
		if ( g == null ) return false;
		if (!g.isValid()) {
			removeObject( g );
			return false;
		}
		else if ( g.changePositionFlag() ) {
			Position p = g.newPosition();
			if ( p.isValid() ) {
				positionCommand( g.objectID(), p );
				
				ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.MOVE_VIEW_OBJECT, g, null);
				viewHandler.fireEventNow(voe);
				
				ret = false;
			}
			g.resetPositionChangedFlag();
			ret = true;
		}
		return ret;
	}
	
	private void removeObject( GameObject g ) {
		/*
		 * In addition to removing an object from the list, we should probably do
		 * something to update the view since this object no longer 'exists'.
		 */
		GameObject selected = getCurrentSelection();
		
		if (playerUnits.getLength() > 1) {
			ObjectID nextUnit = this.playerUnits.getNext();
			boolean successful = false;
			for (int i=0; i<playerUnits.getLength(); i++) {
				if (objectQuery(nextUnit) != null) {
					successful = true;
					break;
				}
				nextUnit = this.playerUnits.getNext();
			}
			if (successful) {
				this.setPlayerIterators(this.playerIDQuery(nextUnit));
				boolean checkBases = true;
				for( int j=0; j<playerUnits.getLength(); j++) {
					ObjectID oid = playerUnits.getNext();
					Unit u = (Unit)objectQuery(oid);
					if (!u.getMissionFlag()) { 
						setCurrentSelection(nextUnit);
						checkBases = false;
						break;
					}
				}
				if (checkBases && playerBases.getLength() > 0) {
					setCurrentSelection(playerBases.getNext());
				}
			}
			else
			{
			ObjectID nextBase = this.playerBases.getNext();
			if (nextBase == null || objectQuery(nextBase) == null) {
				System.out.println("YOU LOSE!!!");
				System.exit(0);
			}
			this.setPlayerIterators(this.playerIDQuery(nextBase));
			setCurrentSelection(nextBase);
			}
		}
		for ( GameData gd : dataList ) {
			if (gd.removeObject( g ))
			{
				ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.REMOVE_VIEW_OBJECT, g, null);
				viewHandler.fireEventNow(voe);
				
				break;
			}
		}
		pMap.removeObject( g );
		oMap.removeObject( g );
	}	
	
	public void addPlayer(Player p)
	{
		players.add(p);
	}
	
	public Player getPlayer(int playerId)
	{
		if(!validatePlayerID(playerId))
		{
			System.out.println(playerId + " is not a valid Player ID");
			return null;
		}
			
		for( Player p : players)
		{
			if(p.getID() == playerId)
				return p;
		}
		return null;
	}
	
	private boolean validatePlayerID(int id)
	{
		boolean valid = false;
		for(int i = 0; i < players.size(); i++)
		{
			if(players.elementAt(i).getID() == id)
			{
				valid = true;
				break;
			}
		}
		
		if(valid)
		{
		    return true;
		}
		return false;
	}
	
	public Position getCurrentPosition()
	{
		if(currentPosition != null)
			return currentPosition;
		else
		{
			System.out.println("Nothing is selected yet");
			return null;
		}
	}
	
	public void setCurrentPosition(Position p)
	{
		currentPosition = p;
		ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.SET_CURRENT_POSITION, null, p);
		viewHandler.fireEventNow(voe);
	}
	
	public ObjectID getCurrentSelectionID()
	{
		if(currentSelection != null)
			return currentSelection;
		else
		{
			System.out.println("Nothing is selected yet");
			return null;
		}
	}
	
	public GameObject getCurrentSelection()
	{
		if(currentSelection != null)
			return objectQuery(currentSelection);
		else
		{
			System.out.println("Nothing is selected yet");
			return null;
		}
	}
	
	public void setCurrentSelection(ObjectID id)
	{
		this.currentSelection = id;
		this.currentPosition = positionQuery(currentSelection);
		GameObject go = this.objectQuery(id);
		ViewObjectEvent voe = new ViewObjectEvent(-1, ViewEventType.SELECT_VIEW_OBJECT, go, positionQuery(id));
		viewHandler.fireEventNow(voe);
	}
	
	public Vector<Unit> getPlayersUnits(int playerId)
	{
		if(!validatePlayerID(playerId))
		{
			System.out.println(playerId + " is not a valid Player ID");
			return null;
		}
		ObjectID[] oids = oMap.getPlayersEntities(playerId);
		if(oids == null)
		{
			MainScreen.writeToConsole("DataHandler:  Current Player has no entities");
			return null;
		}
		Vector<Unit> ret = new Vector<Unit>();
		
		for(int i = 0; i<oids.length; i++)
		{
			GameObject g = objectQuery(oids[i]);
			if(g != null && g.objectType() == GameObjectType.UNIT)
				ret.add((Unit)g);
		}
		return ret;
	}
	
	public Vector<Base> getPlayersBases(int playerId)
	{
		if(!validatePlayerID(playerId))
		{
			System.out.println(playerId + " is not a valid Player ID");
			return null;
		}
		ObjectID[] oids = oMap.getPlayersEntities(playerId);
		if(oids == null)
		{
			MainScreen.writeToConsole("DataHandler:  Current Player has no entities");
			return null;
		}
		Vector<Base> ret = new Vector<Base>();
		
		for(int i = 0; i<oids.length; i++)
		{
			GameObject g = objectQuery(oids[i]);
			if(g != null && g.objectType() == GameObjectType.BASE)
				ret.add((Base)g);
		}
		return ret;
	}
	
	public Vector<Entity> getPlayersObjects(int playerId){

		if(!validatePlayerID(playerId))
		{
			System.out.println(playerId + " is not a valid Player ID");
			return null;
		}
		ObjectID[] oids = oMap.getPlayersEntities(playerId);
		Vector<Entity> ret = new Vector<Entity>();
		
		for(int i = 0; i<oids.length; i++)
		{
			GameObject g = objectQuery(oids[i]);
			ret.add((Entity)g);
		}
		return ret;
	}
	
	public void setPlayerIterators(int playerId) {
		Vector<Unit> u = getPlayersUnits(playerId);
		if(u == null)
		{
			MainScreen.writeToConsole("DataHandler:  Current Player has no units.  UnitIterator is null");
			this.playerUnits = null;
		}
		ObjectID[] oids = new ObjectID[u.size()];
		int i = 0;
		for ( Unit unit : u ) {
			oids[i] = unit.objectID();
			i++;
		}
		this.playerUnits = new EatAtJoesIterator(oids);
		Vector<Base> b = getPlayersBases(playerId);
		if(b == null)
		{
			MainScreen.writeToConsole("DataHandler:  Current Player has no bases.  BaseIterator is null");
			this.playerBases = null;
		}
		oids = new ObjectID[b.size()];
		i = 0;
		for ( Base base : b ) {
			oids[i] = base.objectID();
			i++;
		}
		this.playerBases = new EatAtJoesIterator(oids);
		ObjectID selected = playerUnits.getNext();
		if (selected == null) {
			ObjectID nextUnit = this.playerUnits.getNext();
			if (!(nextUnit == null)) {
				this.setPlayerIterators(this.playerIDQuery(nextUnit));
				setCurrentSelection(nextUnit);
			}
			else
			{
				ObjectID nextBase = this.playerBases.getNext();
				if (nextBase == null) {
					System.out.println("YOU LOSE!!!");
					System.exit(0);
				}
				this.setPlayerIterators(this.playerIDQuery(nextBase));
				setCurrentSelection(nextBase);
			}
		}
		setCurrentSelection(selected);
	}
	

	public EatAtJoesIterator getUnitIterator() {
		if(this.playerUnits.getLength() == 0)
		{
			MainScreen.writeToConsole("DataHandler:  Giving null UnitIterator");
			return null;
		}
		return this.playerUnits;
	}
	
	public EatAtJoesIterator getBaseIterator() {
		if(this.playerBases.getLength() == 0)
		{
			MainScreen.writeToConsole("DataHandler:  Giving null BaseIterator");
			return null;
		}
		return this.playerBases;
	}
}
